import glob
import itertools
import math
import numexpr
import os.path
import re
import shutil
import sys
import tempfile

from histogram import HistogramCollection

class BadCommandLineError(Exception):
    def __init__(self, msg, retcode):
        self.msg = msg
        self.retcode = retcode

    def __str__(self):
        return self.msg

           
def safe_mkdir(path, default=None, force=False, force_opt='--force-log'):
    if path is None:
        if default is None:
            return None
        path = os.path.abspath(default)
    
    if os.access(path, os.F_OK):
        if not os.path.isdir(path):
            msg = "Output path `" + path 
            msg += "' already exists as something other than a directory.\n"
            msg += "Please specify another output path, "
            msg += "remove the existing one, or use the `{0}' option."
            raise BadCommandLineError(msg.format(force_opt), -3)
        elif not force:
            msg = "Output path `" + path + "' already exists.\n"
            msg += "Please specify another output path, "
            msg += "remove the existing one, or use the `{0}' option."
            raise BadCommandLineError(msg.format(force_opt), -4)
    else:
        os.mkdir(path)
            
    return path
        

def module_name_from_paths(paths):
    import tables
    from collections import defaultdict

    with tables.openFile(paths[0]) as fp:
        if 'module' in fp.root.nt.attrs:
            return fp.root.nt.attrs['module']
        else:
            msg = "Path `{0}' doesn't provide a `module' attribute;"
            msg += " use `-m' to specify manually".format(paths[0])
            raise BadCommandLineError(msg, -110)
    

    # Full error-checking version below
    modattrs = defaultdict(list)

    for path in paths:
        fp = tables.openFile(path)

        if hasattr(fp.root.nt.attrs, 'module'):
            modattrs[fp.root.nt.attrs.module].append(path)
                    
        fp.close()

    if len(modattrs.keys()) == 0:
        msg = "No input file provides a `module' attribute; "
        msg += "use `-m' to specify manually"
        raise BadCommandLineError(msg, -110)
    
    if len(modattrs.keys()) > 1:
        msg = "Multiple modules found in input files: \n"
        for key in modattrs:
            msg += "\t" + key + ':\n'
            for path in modattrs[key]:
                msg += "\t\t" + path + '\n'
        
        raise BadCommandLineError(msg, -111)

    
    return modattrs.keys()[0]
    
def column_names_from_paths(paths):
    import tables
    from collections import defaultdict

    with tables.openFile(paths[0]) as fp:
        return fp.root.nt.colnames

    # Full error-checking version below
    column_names = None
    ncols = defaultdict(list)

    for path in paths:
        fp = tables.openFile(path)

        if column_names is None:
            column_names = set(fp.root.nt.colnames)
            
        ncols[len(fp.root.nt.colnames)].append(path)

        fp.close()

    if len(ncols.keys()) > 1:
        msg = "Multiple row definitions found in input files: \n"
        for key in ncols:
            msg += "\t" + key + ' columns:\n'
            for path in ncols[key]:
                msg += "\t\t" + path + '\n'

        raise BadCommandLineError(msg, -120)

    return list(column_names)
    

def h1d_names(module, hnames):
    h1d_names = []
    for hn in hnames:
        h = module.hists[hn]
        if hasattr(h, 'field'):
            h1d_names.append(hn)

    return h1d_names

def h2d_names(module, hnames):
    h2d_names = []
    for hn in hnames:
        h = module.hists[hn]
        if hasattr(h, 'yfield') and not hasattr(h, 'zfield'):
            h2d_names.append(hn)

    return h2d_names

def h3d_names(module, hnames):
    h3d_names = []
    for hn in hnames:
        h = module.hists[hn]
        if hasattr(h, 'zfield'):
            h3d_names.append(hn)

    return h3d_names


def how_fill(module, hnames, cols):
    bycol = []
    byvec = []
    
    table_cols = set(cols)
    for hn in hnames:
        assoc_cols = set(module.hists[hn].assoc_cols)
  
        if len(assoc_cols) == 0 or not assoc_cols.issubset(table_cols):
            byvec.append(hn)
        else:
            bycol.append(hn)

    return bycol, byvec


def groups_of_n(n, iterable, padvalue=None):
    "groups(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return itertools.izip(*[itertools.chain(iterable, itertools.repeat(padvalue, n-1))]*n)


def n_groups(n, iterable):
    if n == 0:
        return []
    ret = tuple([[] for i in xrange(n)])

    for i, x in enumerate(iterable):
        ret[i % n].append(x)

    return ret


def safe_import(package_name):
    try:
        package      = __import__(package_name)
    except ImportError, e:
        
        sys.stderr.write("Error importing module `{0}':\n".format(package_name))
        sys.stderr.write(str(e) + '\n')
        sys.stderr.write("Aborting...\n")
        sys.stderr.flush()
        
        raise e

    return package


def column_output(items, prefix='', ncols=3):
    if len(items) < ncols:
        print prefix + '  '.join([item for item in items])
        return

    field_width = max([len(item) for item in items])

    nrows = int(math.ceil(float(len(items)) / ncols))

    columns = [group for group in groups_of_n(nrows, items, '')]

    row_string = ''
    for i in xrange(len(columns)):
        row_string += '{' + str(i) + ':<' + str(field_width) + '}'
        row_string += '    '

    row_string = row_string[:-1]

    for i in xrange(nrows):
        row = [columns[j][i] for j in xrange(len(columns))]

        print prefix+row_string.format(*row)

def histogram_assoc_cols(module, names=None):
    assoc_cols = set()

    if names is None:
        names = module.hists.keys()

    for n in names:
        for ac in module.hists[n].assoc_cols:
            assoc_cols.add(ac)

    return assoc_cols
    


def assert_fillable(colc, hcoll, table_cols):
    table_cols = set(table_cols)
    cols_required = hcoll.assoc_cols()

    cols_missing = cols_required - table_cols

    for col in cols_missing:
        if col not in colc:
            msg = "No function of name `{}' found in ColumnCalculator"
            msg += "Column `{}' is not present in the data but is required by\n"
            msg += " histograms " 
            msg += ' '.join([h.name for h in hcoll.col_used_by(col)])

            raise AttributeError(msg.format(col, col))


def hmod_load_and_apply(hd):
    import sys

    if '__hmod__' not in hd:
        return hd
    
    sys.path.append(os.getcwd())
        
    try:
        module_name, hmod_name = hd['__hmod__']
        module = __import__(module_name)

    except ImportError, e:
        sys.stderr.write("Dictionary in `{0}' has `__hmod__' attribute but module `{1}'".format(infname,
                                                                                                hd['__hmod__'][0]))
        sys.stderr.write(" specified therein cannot be imported:\n")
        sys.stderr.write(str(e) + '\n')
        sys.stderr.write("Aborting...\n")
        raise e
            
    if hasattr(module, hmod_name):
        hmod = getattr(module, hmod_name)
                
    for key in hd:
        for attr in 'title xlabel xunits ylabel yunits zlabel zunits logz'.split():
            if hasattr(hd[key], attr) and key in hmod:
                setattr(hd[key], attr, getattr(hmod[key], attr))
                
    del hd['__hmod__']
    
    
def build_histcoll(module, opts):
    if len(opts.hcolls) == 0 and len(opts.list) == 0:
        try:
            return module.default_hcoll
        except KeyError:
            msg = "No histogram names or collections supplied, "
            msg += "but `{0}.hcolls' does not provide a default collection"
            raise KeyError(msg.format(module.__name__))

    ret_hcoll = HistogramCollection(module_name=module.__name__)

    if len(opts.hcolls) > 0:
        for hcnm in opts.hcolls:
            for hist in module.hcolls[hcnm]:
                ret_hcoll.add(hcnm)
        
    if len(opts.list) > 0:
        for hnm in opts.list:
            if hnm not in module.hnames:
                msg = "No histogram `{0}' in any of the collections in `{1}'"
                raise KeyError(msg.format(hnm, module.__name__))
            
            if len(module.hnames[hnm]) > 1:
                msg = "More than one histogram in {} with name `{}'"
                msg += "\n (collections {} each have one)"
                raise NotImplementedError(msg.format(
                        module.__name__, hnm, ', '.join(module.hnames[hnm])))

            cname = list(module.hnames[hnm])[0]
            ret_hcoll.add(module.hcolls[cname][hnm])
            
    return ret_hcoll
        
                
                    
    
    



    
        

            
        
        
